import { defineVirtualModule, generageImports } from './_utils'
import utilsModule from './utils'
import wasmModule from './wasm'
import { isFunction, once } from 'lodash-es'
import chalk from 'chalk'
import { resolveModuleId, safeRequire } from '../../utils'
import which from 'which'
import path from 'path'
import fs from 'fs'
import babelGenerator from '@babel/generator'
import { parse } from '@babel/parser'
import * as t from '@babel/types'

const _fsRead = (p: string | undefined | null) => {
  try {
    if (p && typeof p === 'string') {
      return fs.readFileSync(p).toString()
    }
  } catch {}
}

const _which = (c: string) => {
  let ex = which.sync(c, { nothrow: true })
  try {
    if (ex) {
      while (fs.statSync(ex).isSymbolicLink()) {
        ex = path.join(path.dirname(ex), fs.readlinkSync(ex))
      }
    }
  } catch (e) {}
  return ex
}

export const GO = _which('go')
export const TINY_GO = _which('tinygo')

const optCode = (code: string) => {
  code = code.replace(/\bglobal\s*\.\s*require\s*\.\s*main\s*===\s*module\b/g, 'false')
  try {
    const ast = parse(code)
    const body = ast.program.body
    const [c0] = body
    if (body.length === 1 && t.isExpressionStatement(c0)) {
      const { expression } = c0
      if (t.isCallExpression(expression)) {
        const { callee, arguments: args } = expression
        if (t.isFunction(callee) && args.length === 0) {
          if (!callee.async && !callee.generator) {
            const calleeBody = callee.body
            body[0] = t.isBlockStatement(calleeBody)
              ? t.cloneNode(calleeBody)
              : t.expressionStatement(t.cloneNode(calleeBody))
            code = babelGenerator(ast).code
          }
        }
      }
    }
  } catch {}
  return code
}

const goWasmExecModule = defineVirtualModule(
  'wasm-virutal:wasm-golang',
  once(() => {
    return `
      let $_Go, $_TinyGo;
      const initGo = () => {
        if($_Go) return $_Go;
        ${optCode(
          _fsRead(GO ? path.join(path.dirname(GO), '../misc/wasm/wasm_exec.js') : '') ??
            `throw new Error('No such file: $GO_HOME/misc/wasm/wasm_exec.js')`
        )};
        return $_Go = Go;
      }
      const initTinyGo = () => {
        if($_TinyGo) return $_TinyGo;
        ${optCode(
          _fsRead(TINY_GO ? path.join(path.dirname(TINY_GO), '../targets/wasm_exec.js') : '') ??
            `throw new Error('No such file: $TINYGO_HOME/targets/wasm_exec.js')`
        )};
        return $_TinyGo = Go;
      }
    `
  }),
  ['initGo', 'initTinyGo']
)

export default defineVirtualModule(
  'wasm-virutal:wasm-golang-helper',
  once(() => {
    return `
      ${generageImports(
        [goWasmExecModule, goWasmExecModule.exports],
        [wasmModule, ['createInstantiateFromBuffer', 'createInstantiateFromPath']]
      )}
      
      const _start = (instance, go) => {
        return go.run(instance);
      }

      const common_factory = async (createInstance, __importObject, asynchronous, go) => {
        const { importObject } = go;
        if(__importObject) {
          for(const modName in __importObject) {
            const mod = __importObject[modName];
            if(mod && typeof mod === 'object') {
              Object.assign(importObject[modName] = importObject[modName] ?? {}, mod);
            } else {
              importObject[modName] = mod;
            }
          }
        }
        const instanceP = createInstance(importObject);
        if (asynchronous) {
          await _start(await instanceP, go)
        } else {
          _start(instanceP, go);
        }
      }

      const go_factory = (createInstance, __importObject, asynchronous) => {
        return common_factory(createInstance, __importObject, asynchronous, new (initGo())());
      }

      const tinygo_factory = (createInstance, __importObject, asynchronous) => {
        return common_factory(createInstance, __importObject, asynchronous, new (initTinyGo())());
      }
      
      const go_createInstantiateFromPath = (wasmPath) => {
        return createInstantiateFromPath(wasmPath, go_factory)
      }

      const go_createInstantiateFromBuffer = (wasmSource) => {
        return createInstantiateFromBuffer(wasmSource, go_factory)
      }

      const tinygo_createInstantiateFromPath = (wasmPath) => {
        return createInstantiateFromPath(wasmPath, tinygo_factory)
      }

      const tinygo_createInstantiateFromBuffer = (wasmSource) => {
        return createInstantiateFromBuffer(wasmSource, tinygo_factory)
      }
    `
  }),
  [
    'go_createInstantiateFromPath',
    'go_createInstantiateFromBuffer',
    'tinygo_createInstantiateFromPath',
    'tinygo_createInstantiateFromBuffer',
  ]
)
